#include <qtextstream.h>

#include "playlistentry.h"
#include "playlist.h"
#include "audiofile.h"
#include "tagreaderthread.h"

#include "debugmodule.h"

QString Playlist::sm_replacePath = QString();
QString Playlist::sm_localPath = QString();
TagReaderThread* Playlist::sm_pTagThread = NULL;

Playlist::Playlist(const QString& i_filepath, PlaylistType i_type, QObject *i_pParent) :
    QObject(i_pParent),
    m_fileInfo(i_filepath),
    m_filename(m_fileInfo.completeBaseName()),
    m_type(i_type),
    m_missingCount(0), m_duplicateCount(0),
    m_incompleteCount(0),
    m_hasChanges(false)
{
}

Playlist::~Playlist()
{
    qDeleteAll(m_entries);
}

QString Playlist::typeString() const
{
    switch(m_type)
    {
    case M3U:
        return QStringLiteral("M3U");
    case MPL:
        return QStringLiteral("MPL");
    default:
        return QStringLiteral("Unbekannt");
    }
}

void Playlist::checkAudioFiles(const QHash<QString, AudioFile*> &i_audioFiles)
{
    for (int i = 0; i < m_entries.size(); i++)
    {
        // zugehöriges Audiofile suchen
        // Pfad in Playlisteneintrag wird mit lokalem Musikpfad ersetzt
        QString path(m_entries[i]->filepath());

        if (!sm_replacePath.isEmpty() && !sm_localPath.isEmpty())
            path.replace(sm_replacePath, sm_localPath);

        m_entries[i]->setAudioFile(i_audioFiles.value(path, NULL));
        m_entries[i]->setFilePath(path);
    }

    checkMissing();
}

void Playlist::addAudioFiles(const QList<AudioFile *> &i_audioFiles)
{
    QList<PlaylistEntry*> entriesToRefresh;

    foreach (AudioFile* pFile, i_audioFiles)
    {
        PlaylistEntry* pEntry = new PlaylistEntry(this, pFile, pFile->fileInfo().absoluteFilePath());

        if (pEntry->isMissing())
            ++m_missingCount;

        ++m_incompleteCount;
        connect(pEntry, SIGNAL(completeStateChanged(bool)), SLOT(adjustCompleteState(bool)));

        m_entries << pEntry;
        entriesToRefresh << pEntry;
    }

    if (!i_audioFiles.isEmpty())
    {
        m_hasChanges = true;
        emit dataChanged();

        // Tags abrufen
        if (sm_pTagThread)
            sm_pTagThread->refreshEntries(entriesToRefresh);
    }
}

bool Playlist::save()
{
    QFile file(m_fileInfo.absoluteFilePath());

    if (file.open(QFile::WriteOnly | QFile::Truncate))
    {
        // Erst sortieren
        std::sort(m_entries.begin(), m_entries.end(), PlaylistEntry::lessThan);

        QTextStream stream(&file);
        stream.setCodec("UCS-2");
        stream.setGenerateByteOrderMark(true);

        foreach (const PlaylistEntry* pEntry, m_entries)
        {
            QString path(pEntry->filepath());

            // Pfad ersetzen
            if (!sm_replacePath.isEmpty() && !sm_localPath.isEmpty())
                path.replace(sm_localPath, sm_replacePath);

            // Slash durch Backslash ersetzen
            path.replace('/', '\\');

            if (m_type == MPL)
            {
                stream << pEntry->album() << '|'
                       << pEntry->artist() << '|'
                       << pEntry->genre() << '|'
                       << pEntry->title() << '|'
                       << pEntry->track() << '|'
                       << pEntry->year() << '|'
                       << pEntry->date1() << '|'
                       << pEntry->date2() << '|'
                       << pEntry->empty1() << '|'
                       << pEntry->empty2() << '|'
                       << path << '\n';
            }
            else if (m_type == M3U)
            {
                stream << path << '\n';
            }

            if (stream.status() != QTextStream::Ok)
                break;
        }

        if (stream.status() == QTextStream::Ok)
        {
            m_hasChanges = false;
            emit dataChanged();
            return true;
        }
    }

    return false;
}

void Playlist::removeEntry(int i_idx)
{
    PlaylistEntry* pEntry = m_entries.takeAt(i_idx);

    if (pEntry->isMissing())
        --m_missingCount;

    if (!pEntry->isCompleteAndValid())
        --m_incompleteCount;

    delete pEntry;
    setChanged();
}

bool Playlist::setFilepath(const QString& i_path)
{
    const PlaylistType type = getTypeFromFilename(i_path);

    if (type != Unknown)
    {
        const QFileInfo newPath(i_path);

        if (newPath != m_fileInfo)
        {
            m_fileInfo = newPath;
            m_filename = m_fileInfo.completeBaseName();
            m_type = type;
            setChanged();
        }

        return true;
    }

    return false;
}

Playlist* Playlist::parse(const QString& i_filepath,
                         const QHash<QString, AudioFile*>& i_audioFiles)
{
    QFile file(i_filepath);

    if (file.open(QFile::ReadOnly | QFile::Text))
    {
        const PlaylistType type = getTypeFromFilename(i_filepath);

        if (type == Unknown)
            return NULL;

        Playlist* pPlaylist = new Playlist(i_filepath, type);
        if (pPlaylist->parse(file, i_audioFiles))
            return pPlaylist;
        else
            delete pPlaylist;
    }

    return NULL;
}

Playlist::PlaylistType Playlist::getTypeFromFilename(const QString &i_filename)
{
    const QFileInfo fi(i_filename);
    const QString& ext = fi.suffix();

    if (ext == QStringLiteral("m3u"))
        return M3U;
    else if (ext == QStringLiteral("mpl"))
        return MPL;
    else
        return Unknown;
}

bool Playlist::parse(QFile& i_file, const QHash<QString, AudioFile*>& i_audioFiles)
{
    qDeleteAll(m_entries);
    m_entries.clear();
    m_duplicateCount = 0;
    m_incompleteCount = 0;
    QTextStream stream(&i_file);

    while(!stream.atEnd())
    {
        const QString& line = stream.readLine();
        PlaylistEntry* pEntry = NULL;

        if (m_type == MPL)
        {
            const QStringList& splits = line.split('|');

            if (splits.length() == 11)
            {
                // Backslash durch Slash ersetzen
                QString path(splits.at(10));
                path.replace('\\', '/');

                // Beim suchen der Audiodatei wird der Pfad im Playlisteneintrag mit lokalem Musikpfad ersetzt
                if (!sm_replacePath.isEmpty() && !sm_localPath.isEmpty())
                    path.replace(sm_replacePath, sm_localPath);

                pEntry = new PlaylistEntry(this,
                                           i_audioFiles.value(path, NULL),
                                           path, splits.at(3), splits.at(1), splits.at(0),
                                           splits.at(2), splits.at(4), splits.at(5), splits.at(6),
                                           splits.at(7), splits.at(8), splits.at(9));
            }
        }
        else if (m_type == M3U)
        {
            if (!line.isEmpty() && !line.startsWith(QStringLiteral("#EXT")))
            {
                QString path(line);

                // Backslash durch Slash ersetzen
                path.replace('\\', '/');

                // Beim suchen der Audiodatei wird der Pfad im Playlisteneintrag mit lokalem Musikpfad ersetzt
                if (!sm_replacePath.isEmpty() && !sm_localPath.isEmpty())
                    path.replace(sm_replacePath, sm_localPath);

                pEntry =  new PlaylistEntry(this,
                                            i_audioFiles.value(path, NULL),
                                            path);
            }
        }

        if (pEntry)
        {
            if (!pEntry->isCompleteAndValid())
                ++m_incompleteCount;

            connect(pEntry, SIGNAL(completeStateChanged(bool)), SLOT(adjustCompleteState(bool)));
            m_entries << pEntry;
        }
    }

    checkMissing();
    return !m_entries.isEmpty() && stream.status() == QTextStream::Ok;
}

void Playlist::checkMissing()
{
    m_missingCount = 0;

    foreach (const PlaylistEntry* entry, m_entries)
    {
        if (entry->isMissing())
            ++m_missingCount;
    }
}

void Playlist::setChanged()
{
    m_hasChanges = true;
    emit dataChanged();
}

void Playlist::adjustCompleteState(bool i_isComplete)
{
        if (i_isComplete)
            --m_incompleteCount;
        else
            ++m_incompleteCount;
}
